//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using Microsoft.Research.ScientificWorkflow.TridentComposer;
using Microsoft.Research.ScientificWorkflow.TridentModel;
using Microsoft.Research.ScientificWorkflow.TridentUtilities;
using Er = Microsoft.Research.DataLayer;
using TridentAPI;

namespace Microsoft.Research.ScientificWorkflow.UIDesigner
{
    /// <summary>
    /// The Flags to specify the validation mode of the Property list.
    /// </summary>
    public enum ValidationMode
    {
        // Make the property invalid only if there exists a invalid value.
        InvalidValue,

        // Make the property invalid only if the value is left empty.
        NullValue,

        // Make the property invalid either if the value is left empty or contains invalid value.
        All,

        // No validations.
        None
    }

    /// <summary>
    /// Interaction logic for PropertyListControl.xaml
    /// </summary>
    public partial class PropertyListControl : UserControl
    {
        /// <summary>
        /// Initialize a new instance of the property list.
        /// </summary>
        /// <param name="parameterDescriptionList">A collection of parameters.</param>
        public PropertyListControl(Collection<ParameterDescriptionModel> parameterDescriptionList, ValidationMode validationMode)
        {
            if (parameterDescriptionList == null)
            {
                throw new ArgumentNullException("parameterDescriptionList");
            }
            this.Parameters = new ObservableCollection<ParameterDescriptionModel>(parameterDescriptionList);
            this.CurrentValidationMode = validationMode;

            this.DataContext = this;
            InitializeComponent();
        }


        /// <summary>
        /// Initialize a new instance of the property list.
        /// </summary>
        public PropertyListControl()
        {
            InitializeComponent();
            this.Parameters = new ObservableCollection<ParameterDescriptionModel>();
            this.DataContext = this;
        }

        #region Public Properties
        /// <summary>
        /// Gets the parameters to be displayed in the property list.
        /// </summary>
        public ObservableCollection<ParameterDescriptionModel> Parameters { get; set; }

        /// <summary>
        /// Gets or sets the composer control instance. Null if used outside the property pane.
        /// </summary>
        public ComposerControl ComposerControl { get; set; }

        /// <summary>
        /// Flag to check if all the properties have valid values.
        /// </summary>
        public static readonly DependencyProperty PropertiesValidProperty =
        DependencyProperty.Register("PropertiesValid", typeof(Boolean), typeof(PropertyListControl), new PropertyMetadata(true));

        /// <summary>
        /// Gets the flag which checks if all properties are valid in the list.
        /// </summary>
        public bool PropertiesValid
        {
            get { return (bool)GetValue(PropertiesValidProperty); }
            set { SetValue(PropertiesValidProperty, value); }
        }

        /// <summary>
        /// 
        /// </summary>
        public ValidationMode CurrentValidationMode { get; set; }
        #endregion

        #region Public methods
        /// <summary>
        /// Clears the list
        /// </summary>
        public void ClearList()
        {
            this.Parameters.Clear();
        }

        /// <summary>
        /// Update the property list.
        /// </summary>
        /// <param name="updatedCollection">The updated list of properties.</param>
        public void UpdateList(Collection<ParameterDescriptionModel> updatedCollection)
        {
            this.Parameters.Clear();

            foreach (ParameterDescriptionModel requiredModel in updatedCollection)
            {
                this.Parameters.Add(requiredModel);
            }
        }

        /// <summary>
        /// Iterates through the parameters and checks if all values are valid.
        /// </summary>
        /// <returns>True if all values are valid. False otherwise.</returns>
        private bool CheckIfAllValuesValid()
        {
            ParameterDescriptionModel model = this.Parameters.FirstOrDefault(p => p.IsValueInvalid);
            return model == null ? true : false;
        }
        #endregion

        #region Event handlers
        /// <summary>
        /// Fired on textbox loaded, assigns its width.
        /// </summary>
        /// <param name="sender">
        /// Value textbox.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnInputValueTextBoxLoaded(object sender, RoutedEventArgs e)
        {
            TextBox txtBx = sender as TextBox;
            (txtBx.TemplatedParent as ContentPresenter).HorizontalAlignment = HorizontalAlignment.Stretch;
            BindingExpression bindingExp = txtBx.GetBindingExpression(TextBox.TextProperty);
            this.ValidateInputValues(txtBx.Text, bindingExp, txtBx);
        }

        /// <summary>
        /// On lost focus of the value textbox, does validations.
        /// </summary>
        /// <param name="sender">
        /// Value textbox.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnInputValueTextBoxLostFocus(object sender, RoutedEventArgs e)
        {
            TextBox txtBx = sender as TextBox;
            BindingExpression bindingExp = txtBx.GetBindingExpression(TextBox.TextProperty);
            this.ValidateInputValues(txtBx.Text, bindingExp, txtBx);

            if (this.ComposerControl != null)
            {
                this.ComposerControl.IsWFCommitted = false;
            }
        }

        /// <summary>
        /// Fired on selection changed of the combo box.
        /// </summary>
        /// <param name="sender">
        /// Combobox.
        /// </param>
        /// <param name="e">
        /// SelectionChangedEventArgs.
        /// </param>
        private void OnDropDownSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            foreach (object obj in e.AddedItems)
            {
                TridentPropertyBindingObject changedVal = obj as TridentPropertyBindingObject;
                changedVal.ParameterDescriptionModel.Value = changedVal.Value;
            }

            if (this.ComposerControl != null)
            {
                this.ComposerControl.IsWFCommitted = false;
            }
        }

        /// <summary>
        /// Fired on text changed of the input value.
        /// </summary>
        /// <param name="sender">
        /// Input value of the textbox.
        /// </param>
        /// <param name="e">
        /// TextChangedEventArgs.
        /// </param>
        private void OnInputValTextChanged(object sender, TextChangedEventArgs e)
        {
            TextBox txtBox = sender as TextBox;
            if (txtBox != null && !txtBox.IsFocused)
            {
                this.OnInputValueTextBoxLostFocus(sender, new RoutedEventArgs());
            }
        }

        /// <summary>
        /// Validates the input values.
        /// </summary>
        /// <param name="enteredVal">
        /// The entered values.
        /// </param>
        /// <param name="bindingExp">
        /// The textbox's binding.
        /// </param>
        /// <param name="errorDck">
        /// The error marker.
        /// </param>
        /// <param name="txtBx">
        /// The textbox.
        /// </param>
        private void ValidateInputValues(string enteredVal, BindingExpression bindingExp, TextBox txtBx)
        {
            if (bindingExp != null && txtBx != null && txtBx.IsVisible)
            {
                ParameterDescriptionModel paramDesc = bindingExp.DataItem as ParameterDescriptionModel;
                paramDesc.IsValueInvalid = !ValidateInputValue(enteredVal, paramDesc, this.CurrentValidationMode);

                // Update the paramModel irrespective of whether it is an error or not. The handling of whether the value is valid or not will be done duing save.
                bindingExp.UpdateSource();

                this.PropertiesValid = this.CheckIfAllValuesValid();
            }
        }

        /// <summary>
        /// Validate the entered value against the requirements of the paramModel.
        /// </summary>
        /// <param name="enteredVal">
        /// The current value.
        /// </param>
        /// <param name="paramDesc">
        /// The parameter model for which the value has been specified.
        /// </param>
        /// <param name="validationMode">
        /// The validation mode.
        /// </param>
        /// <returns>
        /// Returns true if valid value, false otherwise.
        /// </returns>
        public static bool ValidateInputValue(string enteredValue, ParameterDescriptionModel parameterModel, ValidationMode validationMode)
        {
            bool validValue = true;
            if (parameterModel != null || validationMode != ValidationMode.None)
            {
                if (string.IsNullOrEmpty(enteredValue))
                {
                    // If the Error mode is either all or null value validation.
                    if (validationMode == ValidationMode.All || validationMode == ValidationMode.NullValue)
                    {
                        validValue = parameterModel.IsMandatoryForWorkflow ? false : true;
                    }
                }
                else if (validationMode == ValidationMode.All || validationMode == ValidationMode.InvalidValue)
                {
                    try
                    {
                        // If there is a type initializer present.
                        Er.TypeInitializer typeInit = TypeProviderHelper.FetchTypeInitializer(parameterModel.DataType, Er.Runtime.WPF);
                        if (typeInit != null)
                        {
                            string deserializedVal = (parameterModel.Value != null) ? parameterModel.Value.ToString() : string.Empty;
                            BaseTypeInitializer baseTypeinit = ActivityComposer.LoadInitializer(typeInit, deserializedVal);
                            if (baseTypeinit != null)
                            {
                                if (parameterModel.IsMandatory)
                                {
                                    validValue = baseTypeinit.ContainsValidValue();
                                }
                                else
                                {
                                    validValue = baseTypeinit.ContainsValidValue() || baseTypeinit.ContainsEmptyValue();
                                }
                            }
                        }
                        else
                        {
                            // Use the default converters for the system types.
                            System.Type dataType = System.Type.GetType(parameterModel.DataType);
                            if (dataType != null)
                            {
                                object parsedValue = System.ComponentModel.TypeDescriptor.GetConverter(dataType).ConvertFromString(enteredValue);
                                if (null == parsedValue)
                                {
                                    validValue = false;
                                }
                            }
                        }
                    }
                    catch (NotSupportedException)
                    {
                        validValue = false;
                    }
                    catch (FormatException)
                    {
                        validValue = false;
                    }
                    catch (Exception)
                    {
                        validValue = false;
                    }
                }
            }

            return validValue;
        }

        /// <summary>
        /// The loaded event on the combo box.
        /// </summary>
        /// <param name="sender">The origin of the event.</param>
        /// <param name="e">The event args.</param>
        private void ComboBox_Loaded(object sender, RoutedEventArgs e)
        {
            ComboBox comboBox = sender as ComboBox;

            for (int iterator = 0; iterator < comboBox.Items.Count; iterator++)
            {
                TridentPropertyBindingObject valObj = comboBox.Items[iterator] as TridentPropertyBindingObject;
                if (valObj != null && (valObj.Value.Equals(valObj.ParameterDescriptionModel.Value) || valObj.Key.Equals(valObj.ParameterDescriptionModel.Value)))
                {
                    comboBox.SelectedIndex = iterator;
                    break;
                }
            }
            if (comboBox.SelectedIndex == -1 && comboBox.Items.Count > 0)
                comboBox.SelectedIndex = 0;
        }

        /// <summary>
        /// Fired on loaded of the custom type value text box.
        /// </summary>
        /// <param name="sender">
        /// Custom type value textbox.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnCustomTypeValLoaded(object sender, RoutedEventArgs e)
        {
            TextBox box = sender as TextBox;
            if (box != null)
            {
                MultiBindingExpression multiBindingExp = BindingOperations.GetMultiBindingExpression(box, TextBox.TextProperty);
                if (multiBindingExp != null && multiBindingExp.BindingExpressions.Count > 0)
                {
                    BindingExpression bindingExp = multiBindingExp.BindingExpressions[0] as BindingExpression;
                    this.ValidateInputValues(box.Text, bindingExp, box);
                }
            }
        }

        /// <summary>
        /// Fired on text change of the custom type param text box.
        /// </summary>
        /// <param name="sender">
        /// Custom type input value text box.
        /// </param>
        /// <param name="e">
        /// TextChangedEventArgs.
        /// </param>
        private void OnCustomValueTextChanged(object sender, TextChangedEventArgs e)
        {
            this.OnCustomTypeValLoaded(sender, new RoutedEventArgs());
        }

        /// <summary>
        /// Fired on click of the custom type initializer button.
        /// </summary>
        /// <param name="sender">
        /// The custom type initializer button.
        /// </param>
        /// <param name="e">
        /// RoutedEventArgs.
        /// </param>
        private void OnCustomTypeInitClick(object sender, RoutedEventArgs e)
        {
            Button btn = sender as Button;
            DockPanel panel = btn.Parent as DockPanel;
            if (panel != null)
            {
                TextBox box = panel.FindName("txtBxParamValue") as TextBox;
                if (box != null)
                {
                    MultiBindingExpression multiBindingExp = BindingOperations.GetMultiBindingExpression(box, TextBox.TextProperty);
                    if (multiBindingExp != null)
                    {
                        BindingExpression bindingExp = multiBindingExp.BindingExpressions[0] as BindingExpression;
                        if (bindingExp != null)
                        {
                            ParameterDescriptionModel paramDesc = (multiBindingExp.BindingExpressions[0] as BindingExpression).DataItem as ParameterDescriptionModel;
                            if (paramDesc != null)
                            {
                                try
                                {
                                    Er.TypeInitializer typeInit = TypeProviderHelper.FetchTypeInitializer(paramDesc.DataType, Er.Runtime.WPF);
                                    if (typeInit != null)
                                    {
                                        string deserializedVal = (paramDesc.Value != null) ? paramDesc.Value.ToString() : string.Empty;
                                        TypeInitializerContainer typeInitContainer = new TypeInitializerContainer();
                                        BaseTypeInitializer baseTypeinit = typeInitContainer.DoModal(typeInit, deserializedVal);
                                        if (baseTypeinit != null)
                                        {
                                            paramDesc.Value = baseTypeinit.GetSerializedVersion(baseTypeinit.GetValue());
                                            bindingExp.UpdateTarget();
                                            MultiBindingExpression multiBindingExpTooltip = BindingOperations.GetMultiBindingExpression(box, ToolTipProperty);
                                            if (multiBindingExpTooltip != null)
                                            {
                                                BindingExpression bindingExpTooltip = multiBindingExpTooltip.BindingExpressions[0] as BindingExpression;
                                                if (bindingExpTooltip != null)
                                                {
                                                    bindingExpTooltip.UpdateTarget();
                                                }
                                            }

                                            if (this.ComposerControl != null)
                                            {
                                                this.ComposerControl.IsWFCommitted = false;
                                            }
                                        }
                                    }
                                }
                                catch (TridentCustomException ex)
                                {
                                    TridentMessageBox.ShowTridentErrorMessageBox(ex.Message);
                                }
                            }
                        }
                    }
                }
            }
        } 
        #endregion
    }
}
